#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "aboutwindow.h"
#include "aeswindow.h"
#include "config.h"
#include <QFileDialog>
#include <QMessageBox>
#include <QThread>
/// MD5库
#include <QCryptographicHash>
#include <zlib.h>
#include "qaesencryption.h"

QByteArray iv("20231211000gccdy");

aboutwindow *win_about = NULL;
aeswindow *win_aes = NULL;

QByteArray keys_main = NULL;
int keyLen_main = 0,mode_main = 0,padding_main = 0;

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    //菜单栏只能用triggered这个信号
    connect(ui->about_software_action,SIGNAL(triggered()),
            this,SLOT(on_check_about_menu_clicked()));
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::on_selctionProgram_button_clicked()
{
    QString file_name = QFileDialog::getOpenFileName(
        this,
        tr("选择固件"),
        "C:/",
        tr("BIN(*.bin);;LDX(*.ldx)"));

    if(file_name.isEmpty())
    {
        QMessageBox::warning(this,"警告","固件类型错误或未选择固件");
    }
    else
    {
        ui->fileName_lineText->setText(file_name);
        QStringList strList = file_name.split("/");
        QString dir_str = "";
        for(int i = 0; i < strList.count()-1; i++)
        {
            dir_str += strList.at(i);
            dir_str += "/";
        }
        ui->output_path_label->setText(dir_str);
    }
}


void MainWindow::on_check_gzip_clicked(bool checked)
{
    if(checked)
    {
        ui->toolBar->addAction(ui->set_gzip);
    }
    else
    {
        ui->toolBar->removeAction(ui->set_gzip);
    }
}


void MainWindow::on_Check_MD5_clicked(bool checked)
{
    if(checked)
    {
        ui->toolBar->addAction(ui->set_MD5);
    }
    else
    {
        ui->toolBar->removeAction(ui->set_MD5);
    }
}


void MainWindow::on_check_AES_clicked(bool checked)
{
    if(checked)
    {
        ui->toolBar->addAction(ui->set_AES);
    }
    else
    {
        ui->toolBar->removeAction(ui->set_AES);
    }
}


void MainWindow::on_check_about_menu_clicked()
{
    if(win_about == NULL)
    {
        win_about = new aboutwindow;
        connect(win_about,SIGNAL(on_delete_about_window()),this,SLOT(set_about_window_pointer()));
        win_about->show();
    }
}

void MainWindow::set_about_window_pointer()
{
    win_about = NULL;
}

/**
 * @brief 主窗口关闭后关闭所有窗体
 * @param event
 */
void MainWindow::closeEvent(QCloseEvent* event)
{
    if(win_about != NULL)
    {
        win_about->close_about_window();
    }
    if(win_aes != NULL)
    {
        win_aes->close_aes_window();
    }
}

void MainWindow::on_set_MD5_triggered()
{
#if ENABLE_DEBUG
    qDebug()<<"clicked MD5";
#endif
}

/**
 * @brief AES动作选项，创建aes配置界面
 */
void MainWindow::on_set_AES_triggered()
{
    if(win_aes == NULL)
    {
        win_aes = new aeswindow;
        if(win_aes == NULL)
        {
            return;
        }
        connect(win_aes,SIGNAL(send_aes_config_information(QString,int,int,int)),
                this,SLOT(receive_aes_win_info(QString,int,int,int)));
        win_aes->show();
    }
}


void MainWindow::on_set_gzip_triggered()
{
#if ENABLE_DEBUG
    qDebug()<<"clicked gzip";
#endif
}

/**
 * @brief 点击开始加密后执行的槽函数
 */
void MainWindow::on_startButton_clicked()
{
    startEncrypting();
}

/**
 * @brief AES窗口关闭处理槽函数
 * @param keys  密钥（QString）
 * @param mode  模式（int）
 * @param padding   填充模式（int）
 */
void MainWindow::receive_aes_win_info(QString keys, int key_len, int mode, int padding)
{
    win_aes = NULL;
    if(keys.isEmpty())
    {
        return;
    }
    /// 代码实现应该写在这里

    keys_main = keys.toLatin1();
    keyLen_main = key_len;
    mode_main = mode;
    padding_main = padding;

#if ENABLE_DEBUG
    qDebug()<<"keys:"<<keys<<"\n"<<"modeIndex:"<<mode<<"\n"<<"paddingIndex:"<<padding;
#endif
}

/*
 * 函  数: ZlibGetDecodeLength
 * 功  能: 通过要压缩的数据大小，得到压缩后数据的最大大小
 *        以此设置缓冲区
 * 参  数: uLong  InRawLength  压缩前数据大小
 */

int ZlibGetDecodeLength(unsigned long   InRawLength)
{
    return  compressBound(InRawLength);
}

/*
 * 函  数: ZlibGetDecodeLength
 * 功  能: 通过要压缩的数据大小，得到压缩后数据的最大大小
 *        以此设置缓冲区
 * 参  数: QByteArray  InRawData  压缩前数据
 *
 */
int ZlibGetDecodeLength(QByteArray  InRawData)
{
    // 注:
    // ((QByteArray)"Hello").size() == 5
    // 因为当字符串为 QByteArray , '\0' 被忽略
    return  compressBound(InRawData.length());
}

/*
 * 函  数: ZlibCompress
 * 功  能: 执行压缩操作
 * 参  数: QByteArray  &OutEncodeData  压缩后数据
 *        QByteArray  InRawData       要压缩的数据
          int       * nErrorCode      错误代码
 * 说  明: 当 nErrorCode 不为 NULL 时, 若 compress 出错后, 其数据改为
 *          compress 返回的错误代码. 在 zlib.h 中可以找到.
 *        例如: -5  缓冲区长度不够
 * 返回值: 类型 long
 *        返回压缩后的长度, -1 表示压缩失败, 请检查 *nErrorCode .
 */

long ZlibCompress(QByteArray  &OutEncodeData, QByteArray  InRawData,
                              int  *nErrorCode)
{
    Bytef  *EncodeData = NULL;  // 压缩后的数据暂存于此
    int     nFuncRet = Z_ERRNO; // 随便找个错误代码先填着
    long    nOutLength = 0;
    // 申请最大缓冲区
    nOutLength = ZlibGetDecodeLength(InRawData.length());
    EncodeData = new Bytef[nOutLength];
    if (NULL != EncodeData)
    {
        // 调用 compress 函数前, nOutLength 要置为缓冲区大小
        nFuncRet = compress(EncodeData, (uLongf *)&nOutLength, (Bytef *)InRawData.data(),
                            InRawData.length());

        if (Z_OK == nFuncRet)
        {
            // 自己设置好长度为好, 省的 '\0' 被截断了
            OutEncodeData.append((const char *)EncodeData, nOutLength);
        }
        else
        {
            nOutLength = -1;
        }
    }
    delete  EncodeData;

    // 输出错误代码
    if (NULL != nErrorCode)
    {
        *nErrorCode = nFuncRet;
    }
    return  nOutLength;
}
/*
 * 函  数: ZlibUnCompress
 * 功  能: 执行解压操作
 * 参  数: QByteArray  &OutDecodeData  解压后数据
 *        QByteArray  InEncodeData    要解压的数据
          int       * nErrorCode      错误代码
 * 说  明: 当 nErrorCode 不为 NULL 时, 若 uncompress 出错后, 其数据改为
 *          uncompress 返回的错误代码. 在 zlib.h 中可以找到.
 *        例如: -5  缓冲区长度不够
 * 返回值: 类型 long
 *        返回解压后的长度, -1 表示解压失败, 请检查 *nErrorCode .
 */

long ZlibUncompress(QByteArray  &OutDecodeData, QByteArray InEncodeData,
                                int   * nErrorCode = NULL)
{
    Bytef  *DecodeData = NULL; // 解压后的数据缓冲区
    Bytef  *EncodeData_Buffer = NULL;  // 要解压的数据缓冲区, 后面用memcpy放到这
    int     nFuncRet = Z_ERRNO;
    uLongf  nOutLength = 0;

    // 说明:
    // 如果压缩后大小比之前小, 缓冲区大小应该设置多少?
    // 暂且把 ZlibGetDecodeLength 拿来用用, 确保缓冲区够了.
    //nOutLength = ZlibGetDecodeLength(InEncodeData.length());
    nOutLength = InEncodeData.length()*4;   //
#if ENABLE_DEBUG
    qDebug()<<"InEncodeData.length"<<InEncodeData.length();
    qDebug()<<"nOutLength"<<nOutLength;
#endif

    // 为缓冲区申请内存
    DecodeData        =  new Bytef[nOutLength];
    EncodeData_Buffer =  new Bytef[nOutLength];

    if (NULL != DecodeData && NULL != EncodeData_Buffer)
    {
        // 由于 QByteArray 在 data() 函数中会因为'\0'截断
        // (先鄙视一下，那还叫什么字节操作)
        // 文档又说，constData() 效率高
        // 所以用 constData() 获取地址然后 memcpy 自己复制
        memcpy(EncodeData_Buffer, InEncodeData.constData(), InEncodeData.size());

        nFuncRet = uncompress(DecodeData, &nOutLength, (Bytef *)EncodeData_Buffer,
                              InEncodeData.length());
#if ENABLE_DEBUG
        qDebug()<<"nFuncRet"<<nFuncRet;
#endif
        if (Z_OK == nFuncRet)
        {
            OutDecodeData.append((const char *)DecodeData, nOutLength);
        }
        else
        {
            nOutLength = -1;
        }
    }
    else
    {
        nOutLength = -1;
    }

    // 清理 new 产生的内存
    if (NULL != DecodeData)
    {
        delete  DecodeData;
    }

    if (NULL != EncodeData_Buffer)
    {
        delete  EncodeData_Buffer;
    }

    // 输出错误代码
    if (NULL != nErrorCode)
    {
        *nErrorCode = nFuncRet;
    }
    return  nOutLength;

}
/**
 * @brief 压缩数据
 * @param postBody  数据（QByteArray类型）
 * @return 压缩完成的数据
 */
static QByteArray GzipCompress(QByteArray postBody)
{
    QByteArray outBuf;
#if GZIP_DEFAULT
    z_stream c_stream;
    //int windowBits = 15;
    int GZIP_ENCODING = 16;
    if (!postBody.isEmpty())
    {
        c_stream.zalloc = (alloc_func)0;
        c_stream.zfree = (free_func)0;
        c_stream.opaque = (voidpf)0;
        c_stream.next_in = (Bytef *)postBody.data();
        c_stream.avail_in = postBody.size();
        if (deflateInit2(&c_stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
                         MAX_WBITS + GZIP_ENCODING, 8, Z_DEFAULT_STRATEGY) != Z_OK) return QByteArray();
        for (;;) {
            char destBuf[4096] = { 0 };
            c_stream.next_out = (Bytef *)destBuf;
            c_stream.avail_out = 4096;
            int err = deflate(&c_stream, Z_FINISH);
            outBuf.append(destBuf, 4096 - c_stream.avail_out);
            if (err == Z_STREAM_END || err != Z_OK)
            {
                break;
            }
        }
        //auto total = c_stream.total_out;
        deflateEnd(&c_stream);
        //total = c_stream.total_out;
    }

#else
    int *errorCode = NULL;
    long outlength = ZlibCompress(outBuf,postBody,errorCode);

    if(outlength != (-1))
    {
#if ENABLE_DEBUG
        qDebug()<<"OutByte = "<<outBuf.toHex();
        qDebug()<<"outlength = "<<outlength;
#endif
        return outBuf;
    }
    outBuf = postBody;
#endif
    return outBuf;
}

/**
 * @brief 解压缩
 * @param src 目标数据（QByteArray）
 * @return 解压缩的数据
 */
static QByteArray GZipUnCompress(QByteArray src)
{
QByteArray outBuffer;
#if GZIP_DEFAULT

    z_stream strm;
    strm.zalloc = NULL;
    strm.zfree = NULL;
    strm.opaque = NULL;

    strm.avail_in = src.size();
    strm.next_in = (Bytef *)src.data();

    int err = -1;
    err = inflateInit2(&strm, MAX_WBITS + 16);
    if (err == Z_OK) {
        while (true)
        {
            char buffer[4096] = { 0 };
            strm.avail_out = 4096;
            strm.next_out = (Bytef *)buffer;
            int code = inflate(&strm, Z_FINISH);
            outBuffer.append(buffer, 4096 - strm.avail_out);
            // qDebug()<<"ddd"<<code<<Z_OK;
            if (Z_STREAM_END == code)
            {
                break;
            }
        }
    }
    inflateEnd(&strm);

#else
    int *errorCode = NULL;
    long outlength = ZlibUncompress(outBuffer,src,errorCode);

    if(outlength != (-1))
    {
#if ENABLE_DEBUG
        qDebug()<<"OutByte = "<<outBuffer.toHex();
        qDebug()<<"outlength = "<<outlength;
#endif
        return outBuffer;
    }
    outBuffer = src;

#endif
    return outBuffer;
}


//使用AES对数据进行加密
static QByteArray encodedText(QByteArray data)
{

    if(data.isEmpty())
    {
        return 0;
    }
    QAESEncryption encryption((QAESEncryption::Aes)keyLen_main,
                              (QAESEncryption::Mode)mode_main,
                              (QAESEncryption::Padding)padding_main);
    //QString str = QString::fromUtf8(data);
    //对源数据加密
    QByteArray encoded = encryption.encode(data, keys_main, iv);

    return encoded;
}


//使用AES对数据进行解密
static QByteArray decodedText(QByteArray data)
{
    if(data.isEmpty())
    {
        return 0;
    }
    QAESEncryption encryption((QAESEncryption::Aes)keyLen_main,
                              (QAESEncryption::Mode)mode_main,
                              (QAESEncryption::Padding)padding_main);

    //解密
    QByteArray decodedText = encryption.decode(data, keys_main, iv);

    return decodedText;
}

void MainWindow::writeIntermediateFiles(QByteArray array,QString fileName)
{
    /// 最终文件
    QFile result_file(ui->output_path_label->text()+fileName);
    /// 以只写的方式打开文件，成功返回true，失败返回false
    bool ok = result_file.open(QIODevice::WriteOnly);

    if(ok)
    {
        result_file.write(array);
    }
    result_file.close();

#if ENABLE_DEBUG
    qDebug() << array.toHex(); //转化成16进制
#endif
}

/**
 * @brief MD5加密函数，包含MD5选项框的判断
 */
QByteArray MainWindow::MD5Encrypting(QByteArray array)
{
    if(array.isEmpty())
    {
        return 0;
    }

    if(ui->Check_MD5->isChecked() == true)
    {
        QCryptographicHash hash(QCryptographicHash::Md5); ///< Md5加密
        hash.addData(array.toHex()); ///< 添加数据
        QByteArray retArray = hash.result(); ///< 加密后的数据
        ui->state_Browser->append("MD5加密完成");
        return retArray+array;
    }
    return array;
}

QByteArray MainWindow::AESEncrypting(QByteArray array)
{
    QByteArray retArray;
    if(array.isEmpty())
    {
        return 0;
    }
    if(ui->check_AES->isChecked() == true)
    {
        /// 默认是没有keylen的
        if(keyLen_main == AES_NULL)
        {
            return array;
        }
        retArray = encodedText(array);
        ui->state_Browser->append("AES加密完成");
        return retArray;
    }
    ui->state_Browser->append("AES加密失败");
    return array;
}

QByteArray MainWindow::gzipEncrypting(QByteArray array)
{
    QByteArray gzip;

    if(array.isEmpty())
    {
        return 0;
    }

    if(ui->check_gzip->isChecked() == true)
    {
        gzip = GzipCompress(array);
        ui->state_Browser->append("gzip压缩完成");
        return gzip;
    }

    return array;

}

void MainWindow::startEncrypting()
{
    QByteArray  array;
    ui->state_Browser->clear();
    ui->state_Browser->append("开始加密");
    if(!ui->fileName_lineText->text().isEmpty())
    {
        QFile file;            //创建对象

        file.setFileName(ui->fileName_lineText->text());

        //打开文件
        bool isok = file.open(QIODevice::ReadOnly);
        //读文件
        if(isok != true)
        {
            file.close();
            return;
        }
#if 1
    //读文件
        array =  file.readAll();
        ui->state_Browser->append("读取固件");
#if ENABLE_DEBUG
            qDebug() << array; //转化成16进制
#endif
#else
            //一行一行的读
            QByteArray array;
            while(file.atEnd() == false)
            {
                array += file.readLine();
            }
#endif

        file.close();
        ui->state_Browser->append("固件加密...");

        array = MD5Encrypting(array);
        writeIntermediateFiles(array,"Intermediate.jo5");
        array = gzipEncrypting(array);
        writeIntermediateFiles(array,"Intermediate.jog");
        array = AESEncrypting(array);

        ui->state_Browser->append("加密结束");
        writeIntermediateFiles(array,INTERMEDIATE_FILE_NAME);
        ui->state_Browser->append("输出文件"+ui->output_path_label->text()+"result.ldx");
        ui->state_Browser->append("加密完成");
    }
}

void MainWindow::on_fileName_lineText_editingFinished()
{

    QString file_name = ui->fileName_lineText->text();
    QStringList strList = file_name.split("/");
    QString dir_str = "";
    for(int i = 0; i < strList.count()-1; i++)
    {
        dir_str += strList.at(i);
        dir_str += "/";
    }
    ui->output_path_label->setText(dir_str);
}


///////////////////////////////////////////////////////////////////////////////


/**
 * @brief MD5解密函数，包含MD5选项框的判断
 */
QByteArray MainWindow::MD5Decrypt(QByteArray array)
{
    if(array.isEmpty())
    {
        return 0;
    }

    if(ui->Check_MD5->isChecked() == true)
    {
        QCryptographicHash hash(QCryptographicHash::Md5); ///< Md5加密
        hash.addData(array.mid(16).toHex()); ///< 添加数据
        QByteArray retArray = hash.result(); ///< 加密后的数据
        if(retArray == array.left(16))
        {
            ui->state_Browser->append("MD5解密完成");
        }

        return array.mid(16);
    }
    return array;
}

QByteArray MainWindow::AESDecrypt(QByteArray array)
{
    QByteArray retArray;
    if(array.isEmpty())
    {
        return 0;
    }
    if(ui->check_AES->isChecked() == true)
    {
        /// 默认是没有keylen的
        if(keyLen_main == AES_NULL)
        {
            return array;
        }
        retArray = decodedText(array);
        ui->state_Browser->append("AES解密完成");
        return retArray;
    }

    return array;
}

QByteArray MainWindow::gzipDecrypt(QByteArray array)
{
    QByteArray gzip;

    if(array.isEmpty())
    {
        return 0;
    }

    if(ui->check_gzip->isChecked() == true)
    {
        gzip = GZipUnCompress(array);
        ui->state_Browser->append("gzip解压缩完成");
        return gzip;
    }

    return array;

}

void MainWindow::on_decrypt_Button_clicked()
{
    QByteArray  array;
    ui->state_Browser->clear();
    ui->state_Browser->append("开始解密");
    if(!ui->fileName_lineText->text().isEmpty())
    {
        QFile file;            //创建对象

        file.setFileName(ui->fileName_lineText->text());

        //打开文件
        bool isok = file.open(QIODevice::ReadOnly);
        //读文件
        if(isok != true)
        {
            file.close();
            return;
        }
#if 1
        //读文件
        array =  file.readAll();
        ui->state_Browser->append("读取加密固件");
#if ENABLE_DEBUG
        qDebug() << array; //转化成16进制
#endif
#else
    //一行一行的读
        QByteArray array;
        while(file.atEnd() == false)
        {
            array += file.readLine();
        }
#endif

        file.close();

        ui->state_Browser->append("固件解密...");

        array = AESDecrypt(array);
        writeIntermediateFiles(array,"Intermediate.og");
        array = gzipDecrypt(array);
        writeIntermediateFiles(array,"Intermediate.o5");
        array = MD5Decrypt(array);

        ui->state_Browser->append("解密结束");
        writeIntermediateFiles(array,DECRYPT_FILE_NAME);
        ui->state_Browser->append("输出文件"+ui->output_path_label->text()+"restore.bin");
        ui->state_Browser->append("解密完成");
    }
}

